using System;
using BeeDevelopment.Brazil;

namespace Stetson.Emulator 
{
	public partial class TI83Plus : Z80A 
	{
		private bool IsMapMode0 = true;

		private readonly byte[][] RomPages;
		private readonly byte[][] RamPages;


		private byte[] BankA;
		private byte[] BankB;

		private bool BankAWritable = false;
		private bool BankBWritable = false;

		private int FlashWriteStage = 0;
		private byte FlashWriteCommand = 0;
		private bool FlashJustWritten = false;

		private void WriteMemoryMapControl(byte value)
		{
			this.IsMapMode0 = (value & 1) == 0;
			this.TimerFrequency = (value >> 1) & 0x03;
			//this.TimerOverflowCounter = 64 + 80 * this.TimerFrequency;
		}

		private byte CurrentPageA;
		private void WriteMemoryPageA(byte value)
		{
			this.CurrentPageA = value;
			if ((value & 0x40) == 0) {
				this.BankA = this.RomPages[value & 0x1F];
				this.BankAWritable = false;
			} else {
				this.BankA = this.RamPages[value & 0x01];
				this.BankAWritable = true;
			}
		}

		private byte CurrentPageB;
		private void WriteMemoryPageB(byte value)
		{
			this.CurrentPageB = value;
			if ((value & 0x40) == 0) {
				this.BankB = this.RomPages[value & 0x1F];
				this.BankBWritable = false;
			} else {
				this.BankB = this.RamPages[value & 0x01];
				this.BankBWritable = true;
			}
		}

		public override byte ReadMemory(ushort address)
		{
			bool IsWritable;
			ushort PageAddress = (ushort)(address & 0x3FFF);
			byte Data = this.GetBank(address, out IsWritable)[PageAddress];
			if (!IsWritable && FlashJustWritten) {
				FlashJustWritten = false;
				return 0xFF;
			} else {
				return Data;
			}
		}
		
		public void ForceWriteMemory(ushort address, byte value)
		{
			ushort PageAddress = (ushort)(address & 0x3FFF);
			bool IsWritable;
			byte[] Bank = this.GetBank(address, out IsWritable);
			Bank[PageAddress] = value;
		}

		public override void WriteMemory(ushort address, byte value)
		{
			ushort PageAddress = (ushort)(address & 0x3FFF);
			bool IsWritable;
			byte[] Bank = this.GetBank(address, out IsWritable);
			if (IsWritable)
			{
				Bank[PageAddress] = value;
			} 
			else 
			{
			    if (FlashUnlocked)
			    {
    				switch (FlashWriteStage) 
    				{
    					case 0:
    						if (((address & 0xFFF) == 0xAAA) && (value == 0xAA)) 
    						{
    							++FlashWriteStage;
    						}
    						break;
    					case 1:
    						if (((address & 0xFFF) == 0x555) && (value == 0x55)) 
    						{
    							++FlashWriteStage;
    						}
    						else
    							FlashWriteStage = 0;
    						break;
    					case 2:
    						if ((address & 0xFFF) == 0xAAA) 
    						{
    							if (value == 0xA0 || value == 0x80) 
    							{
    								FlashWriteCommand = value;
    								++FlashWriteStage;
    							}
    							else 
    							{
    								FlashWriteStage = 0;
    							}
    						} 
    						else 
    							FlashWriteStage = 0;
    						break;
    					case 3:
    						if (FlashWriteCommand == 0xA0)
    						{
    							Bank[PageAddress] &= value;
    							FlashJustWritten = true;
    							FlashWriteStage = 0;
    						} 
    						else if (((address & 0xFFF) == 0xAAA) && (value == 0xAA)) 
    						{
    							FlashWriteStage = 0;
    						}
    						else if (value == 0xF0)
    							FlashWriteStage = 0;
    						break;
    					case 4:
    						if (((address & 0xFFF) == 0x555) && (value == 0x55)) 
    						{
    							++FlashWriteStage;
    						} 
    						else if (value == 0xF0)
    							FlashWriteStage = 0;
    						break;
    					case 5:
    						throw new NotImplementedException();
    					default:
    						FlashWriteStage = 0;
    						break;
    				}
			    }
			}
		}
		
		private byte GetPageAtAddress(ushort address)
		{
		    if (this.IsMapMode0)
		    {
		        switch (address / PageSize)
		        {
		            case 0:
		                return 0x00;
		            case 1:
		                return this.CurrentPageA;
		            case 2:
		                return this.CurrentPageB;
		            default:
		                return 0x80;
		        }
		    }
		    else
		    {
		        switch (address / PageSize)
		        {
		            case 0:
		                return 0x00;
		            case 1:
		                return (byte)(this.CurrentPageA & 0xFE);
		            case 2:
		                return (byte)(this.CurrentPageA | 0x01);
		            default:
		                return this.CurrentPageB;
		        }
		    }
		}

		private byte[] GetBank(ushort address, out bool isWritable) 
		{
			isWritable = false;
			if (this.IsMapMode0)
			{
				switch (address / PageSize)
				{
					case 0: // 0x0000..0x3FFF
						return this.RomPages[0];
					case 1: // 0x4000..0x7FFF
						isWritable = BankAWritable;
						return this.BankA;
					case 2: // 0x8000..0xBFFF
						isWritable = BankBWritable;
						return this.BankB;
					case 3: // 0xC000..0xFFFF
						isWritable = true;
						return this.RamPages[0];
				}
			}
			else
			{
				switch (address / PageSize)
				{
					case 0: // 0x0000..0x3FFF
						return this.RomPages[0];
					case 1: // 0x4000..0x7FFF
						isWritable = true;
						return this.RamPages[0];
					case 2: // 0x8000..0xBFFF
						isWritable = BankAWritable;
						return this.BankA;
					case 3: // 0xC000..0xFFFF
						isWritable = BankBWritable;
						return this.BankB;
				}
			}
			
			return null; // "Impossible".
		}
	}
}
